package tool

import (
	"bytes"
	"container/list"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/kataras/iris/v12"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"strconv"
	"strings"
	"text/template"
)

var cli *client

type client struct {
	DocOpen int `comment:"0不生成文档 1生成ShowDoc 2生成markDown"`
	Doc     Doc `comment:"doc配置"`
}
type Doc struct {
	Url      string `comment:"showdoc 项目设置->开放api->下面的开放api连接文档"`
	ApiKey   string `comment:"showdoc 项目设置->开放api->api_key"`
	ApiToken string `comment:"showdoc 项目设置->开放api->api_token"`
	DocDir   string `comment:"Markdown 项目文档文件夹 绝对路径"`
	tmp      *template.Template
	usdp     uploadShowDocParam
}

// 目前只支持 gin和iris框架
func EnableDoc(c interface{}, docOpen int, doc Doc) {

	cli = &client{DocOpen: docOpen, Doc: doc}
	if docOpen != 0 {
		var err error
		if docOpen == 1 {
			cli.Doc.tmp, err = template.New("showdoc").Parse(ShowDocTemplateStr)
			if err != nil {
				panic("showdoc模板错误")
			}
		} else {
			cli.Doc.tmp, err = template.New("showdoc").Parse(MarkdownTemplateStr)
			if err != nil {
				panic("markdown模板错误")
			}
		}
		switch app := c.(type) {
		case *gin.Engine:
			app.Use(responseBody)
			afterhandler = append(afterhandler, cli.uploadGinShowDoc)
		case *iris.Application:
			//设置后置拦截
			app.SetExecutionRules(iris.ExecutionRules{
				//Begin: iris.ExecutionOptions{Force: true},
				//Main:  iris.ExecutionOptions{Force: true},
				Done: iris.ExecutionOptions{Force: true},
			})
			app.Done(cli.uploadIrisShowDoc)
		}
	}
}
func BindJSON(c interface{}, req interface{}) error {
	switch ctx := c.(type) {
	case *gin.Context:
		err := ctx.BindJSON(req)
		if err != nil {
			return err
		}
		if cli != nil && cli.DocOpen != 0 {
			ctx.Set("req", req)
		}
	case iris.Context:
		err := ctx.ReadJSON(req)
		if err != nil {
			return err
		}
		if cli != nil && cli.DocOpen != 0 {
			ctx.Values().Set("req", req)
		}
	}

	//验证参数.BindJSON()
	if err := Validate.Struct(req); err != nil {
		return errors.New(TransError(err))
	}

	return nil
}
func BindJSONNotWithValidate(c interface{}, req interface{}) error {
	switch ctx := c.(type) {
	case *gin.Context:
		err := ctx.BindJSON(req)
		if err != nil {
			return err
		}
		if cli != nil && cli.DocOpen != 0 {
			ctx.Set("req", req)
		}
	case iris.Context:
		err := ctx.ReadJSON(req)
		if err != nil {
			return err
		}
		if cli != nil && cli.DocOpen != 0 {
			ctx.Values().Set("req", req)
		}
	}
	return nil
}
func (cli *client) uploadGinShowDoc(c *gin.Context) {
	go func() {
		pageTitle := c.Request.Header.Get("title")
		if pageTitle == "" {
			fmt.Println("not found title in header")
			return
		}
		pageTitle, _ = url.PathUnescape(pageTitle)
		//接口没有文件夹和顺序也没关系
		catName := c.Request.Header.Get("dir")
		if pageTitle != "" {
			catName, _ = url.PathUnescape(catName)
		}
		sNumber, _ := strconv.Atoi(c.Request.Header.Get("number"))
		req, _ := c.Get("req")
		rsp, _ := c.Get("rsp")
		method := c.Request.Method
		cli.Doc.usdp = uploadShowDocParam{
			ApiKey:      cli.Doc.ApiKey,
			ApiToken:    cli.Doc.ApiToken,
			CatName:     catName,
			PageTitle:   pageTitle,
			PageContent: "",
			SNumber:     sNumber,
		}
		cli.saveDoc(method, c.Request.URL.String(), req, rsp)
	}()

}
func (cli *client) uploadIrisShowDoc(c iris.Context) {
	go func() {
		pageTitle := c.Request().Header.Get("title")
		if pageTitle == "" {
			fmt.Println("not found title in header")
			return
		}
		pageTitle, _ = url.PathUnescape(pageTitle)
		//接口没有文件夹和顺序也没关系
		catName := c.Request().Header.Get("dir")
		if pageTitle != "" {
			catName, _ = url.PathUnescape(catName)
		}
		sNumber, _ := strconv.Atoi(c.Request().Header.Get("number"))
		req := c.Values().Get("req")
		rsp := c.Values().Get("rsp")
		method := c.Request().Method
		cli.Doc.usdp = uploadShowDocParam{
			ApiKey:      cli.Doc.ApiKey,
			ApiToken:    cli.Doc.ApiToken,
			CatName:     catName,
			PageTitle:   pageTitle,
			PageContent: "",
			SNumber:     sNumber,
		}
		cli.saveDoc(method, c.Request().URL.String(), req, rsp)
	}()
	c.Next()
}
func (cli *client) saveDoc(method, url string, req interface{}, rsp interface{}) {
	var request, reqParam string
	if strings.ToUpper(method) == "GET" || strings.ToUpper(method) == "DELETE" {
		reqParam = "同rul"
	} else {
		if req != nil {
			b, _ := json.MarshalIndent(req, "", "\t")
			reqParam = string(b)
			vt := reflect.TypeOf(req)
			vv := reflect.ValueOf(req)
			request = DatamapGenerateReq(data{
				Value: vv,
				Type:  vt,
			})
		}
	}
	var resp, respParam string
	if rsp != nil {
		respBody, _ := json.MarshalIndent(rsp, "", "\t")
		respParam = string(respBody)
		vt := reflect.TypeOf(rsp)
		vv := reflect.ValueOf(rsp)
		resp = DatamapGenerateResp(data{
			Value: vv,
			Type:  vt,
		})
	}
	buf := &bytes.Buffer{}
	m1 := map[string]interface{}{
		"title":       cli.Doc.usdp.PageTitle,
		"url":         url,
		"method":      method,
		"req":         request,
		"reqExample":  reqParam,
		"respExample": respParam,
		"resp":        resp,
		"remark":      "",
	}
	if cli.DocOpen == 1 {
		err := cli.Doc.tmp.Execute(buf, m1)
		if err != nil {
			fmt.Println("cli.Doc.tmp.Execute err:" + err.Error())
			return
		}
		//调用showdoc接口
		cli.Doc.usdp.PageContent = buf.String()
		b, err := json.Marshal(cli.Doc.usdp)
		if err != nil {
			fmt.Println("marshal showdoc err:" + err.Error())
			return
		}
		var m map[string]interface{}
		err = newRequest("post", cli.Doc.Url, bytes.NewReader(b), "", &m)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		if m["error_code"].(float64) != 0 {
			fmt.Println(m["error_message"])
		}
	} else {
		err := cli.Doc.tmp.Execute(buf, m1)
		if err != nil {
			fmt.Println("cli.Doc.tmp.Execute err:" + err.Error())
			return
		}
		err = os.MkdirAll(cli.Doc.DocDir+"/"+cli.Doc.usdp.CatName, os.ModePerm)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		file, err := os.OpenFile(cli.Doc.DocDir+"/"+cli.Doc.usdp.CatName+"/"+cli.Doc.usdp.PageTitle+".md", os.O_CREATE|os.O_TRUNC, os.ModePerm)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		defer file.Close()
		file.Write(buf.Bytes())
	}

	return
}

type uploadShowDocParam struct {
	ApiKey      string `json:"api_key"`
	ApiToken    string `json:"api_token"`
	CatName     string `json:"cat_name"`     //文件夹
	PageTitle   string `json:"page_title"`   //标题
	PageContent string `json:"page_content"` //内容
	SNumber     int    `json:"s_number"`     //排序
}

// 生成请求的表
func DatamapGenerateReq(d data) string {
	str := "|参数名|必选|类型|说明|\r\n"
	str += "|:----:|:---:|:-----:|:-----:|\r\n"
	list1 := list.New()
	list1.PushBack(d)
	str1 := ""
	m := map[string]bool{} //防止重复建相同的表结构
	for list1.Len() > 0 {
		str1 += "\r\n"
		e := list1.Remove(list1.Front()).(data)
		for {
			if e.Type.Kind() == reflect.Slice { //如果是切片
				if e.Value.Len() > 0 {
					e.Type = e.Type.Elem()
					e.Value = e.Value.Index(0)
				} else {
					break
				}
			}
			if e.Type.Kind() == reflect.Ptr { //如果是指针
				e.Type = e.Type.Elem()
				e.Value = e.Value.Elem()
			} else {
				break
			}
		}
		if e.Type.Kind() != reflect.Struct {
			continue
		}
		tbl := e.Type.Name() + "\r\n\r\n"
		tbl += reqRecursive(str, e, list1, m)
		if !strings.HasSuffix(tbl, str) { //防止重复建相同的表结构
			str1 += tbl
		}
	}
	return str1
}

func reqRecursive(str string, d data, list1 *list.List, m map[string]bool) string {
	elem := d.Type
	if m[elem.String()] { //防止重复建相同的表结构
		return str
	} else {
		m[elem.String()] = true
	}
	for j := 0; j < elem.NumField(); j++ {
		isNeed := elem.Field(j).Tag.Get("req")
		if isNeed == "-" {
			continue
		}
		validate := elem.Field(j).Tag.Get("validate")
		require := "否"
		if strings.Contains(validate, "require") {
			require = "是"
		}
		gorm := elem.Field(j).Tag.Get("gorm")
		comment := ""
		gormList := strings.Split(gorm, ";")
		for _, v := range gormList {
			comment = elem.Field(j).Tag.Get("comment")
			if comment == "" && strings.Contains(v, "comment") {
				comment = strings.Replace(strings.Split(v, ":")[1], "'", "", -1)
			}
		}
		if comment == "" {
			comment = "暂无,若需要联系开发者"
		}
		json := elem.Field(j).Tag.Get("json")
		if elem.Field(j).Anonymous { //是否镶嵌结构体
			str += reqRecursive("", data{
				Type:  elem.Field(j).Type, // reflect.TypeOf(d.Value.Field(j).Interface()),
				Value: d.Value.Field(j),   //reflect.ValueOf(d.Value.Field(j).Interface()),
			}, list1, m)
			continue
		}
		if elem.Field(j).Type.Kind() == reflect.Interface {
			d1 := data{
				Type:  elem.Field(j).Type, //reflect.TypeOf(d.Value.Field(j).Interface()),
				Value: d.Value.Field(j),   //reflect.ValueOf(d.Value.Field(j).Interface()),
			}
			list1.PushBack(d1)
		}
		str += "|" + strings.ReplaceAll(json, ",omitempty", "") + "|" + require + "|" + elem.Field(j).Type.String() + "|" + comment + "|\r\n"
		if strings.Contains(elem.Field(j).Type.String(), ".") {
			list1.PushBack(data{
				Type:  elem.Field(j).Type,
				Value: d.Value.Field(j),
			})
			continue
		}
	}
	return str
}

// 生成响应的表
func DatamapGenerateResp(d data) string {
	str := "|参数名|类型|说明|\r\n"
	str += "|:----:|:-----:|:-----:|\r\n"
	list1 := list.New()
	list1.PushBack(d)
	str1 := ""
	m := map[string]bool{} //防止重复建相同的表结构
	for list1.Len() > 0 {
		str1 += "\r\n"
		e := list1.Remove(list1.Front()).(data)
		for {
			if e.Type.Kind() == reflect.Slice { //如果是切片
				if e.Value.Len() > 0 {
					e.Type = e.Type.Elem()
					e.Value = e.Value.Index(0)
				} else {
					break
				}
			}
			if e.Type.Kind() == reflect.Ptr { //如果是指针
				e.Type = e.Type.Elem()
				e.Value = e.Value.Elem()
			} else {
				break
			}
		}
		if e.Type.Kind() != reflect.Struct {
			continue
		}
		tbl := e.Type.Name() + "\r\n\r\n"
		tbl += respRecursive(str, e, list1, m)
		if !strings.HasSuffix(tbl, str) { //防止重复建相同的表结构
			str1 += tbl
		}
	}
	return str1
}

func respRecursive(str string, d data, list1 *list.List, m map[string]bool) string {
	elem := d.Type
	if m[elem.String()] { //防止重复建相同的表结构
		return str
	} else {
		m[elem.String()] = true
	}
	for i := 0; i < elem.NumField(); i++ {
		isNeed := elem.Field(i).Tag.Get("resp")
		if isNeed == "-" {
			continue
		}
		json := elem.Field(i).Tag.Get("json")
		comment := elem.Field(i).Tag.Get("comment")
		if comment == "" {
			gorm := elem.Field(i).Tag.Get("gorm")
			gormList := strings.Split(gorm, ";")
			for _, v := range gormList {
				if strings.Contains(v, "comment") {
					comment = strings.Replace(strings.Split(v, ":")[1], "'", "", -1)
				}
			}
		}
		if comment == "" {
			comment = "暂无,若需要联系开发者"
		}
		if elem.Field(i).Anonymous { //是否镶嵌结构体
			str += respRecursive("", data{
				Type:  elem.Field(i).Type,
				Value: d.Value.Field(i),
			}, list1, m)
			continue
		}
		if elem.Field(i).Type.Kind() == reflect.Interface {
			d1 := data{
				Type:  elem.Field(i).Type,
				Value: d.Value.Field(i),
			}
			list1.PushBack(d1)
		}
		str += "|" + strings.ReplaceAll(json, ",omitempty", "") + "|" + elem.Field(i).Type.String() + "|" + comment + "|\r\n"
		if strings.Contains(elem.Field(i).Type.String(), ".") {
			list1.PushBack(data{
				Type:  elem.Field(i).Type,
				Value: d.Value.Field(i),
			})
			continue
		}
	}
	return str
}

type data struct {
	Type  reflect.Type
	Value reflect.Value
}

/*
	func stringTransf(str string) string {
		index := 0
		newstr := ""
		start, end := 0, 0
		ind := 0
		for i, v := range str {
			if str[i] == '"' {
				ind++
				if ind%2 == 1 {
					start = i + 1
				} else {
					end = i
					newstr += `"` + str[start:end] + `"`
					continue
				}
			}
			if ind%2 == 1 {
				continue
			}
			if str[i] == '{' {
				newstr += "{\r\n"
				index++
				for i := 0; i < index; i++ {
					newstr += "\t"
				}
			} else if str[i] == ',' {
				newstr += ",\r\n"
				for i := 0; i < index; i++ {
					newstr += "\t"
				}
			} else if str[i] == '}' {
				index--
				newstr += "\r\n"
				for i := 0; i < index; i++ {
					newstr += "\t"
				}
				newstr += "}"
			} else {
				newstr += string([]byte{byte(v)})
			}
		}
		return newstr
	}
*/
func newRequest(method, url string, body io.Reader, ContentType string, data interface{}) (err error) {
	method = strings.ToUpper(method)
	if method == "POST" {
		client := &http.Client{}
		req, err := http.NewRequest(method, url, body)
		if err != nil {
			return err
		}
		if ContentType == "" {
			req.Header.Set("content-type", "application/json; charset=utf-8")
		} else {
			req.Header.Set("content-type", ContentType)
		}
		resp, err := client.Do(req)
		if err != nil {
			return err
		}
		defer resp.Body.Close()
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		return json.Unmarshal(b, data)
	} else if method == "GET" {
		resp, err := http.Get(url)
		if err != nil {
			return err
		}
		defer resp.Body.Close()
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		return json.Unmarshal(b, data)
	}
	return errors.New("请求方式错误")
}
